﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WebControl.cs" company="">
//   
// </copyright>
// <summary>
//   A standard web html control
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using Quail.Helpers;
using Selenium;

namespace Quail
{
    /// <summary>
    /// add extensions for blur and blur while setting the value
    /// </summary>
    public static class WebControlExtensions
    {
        /// <summary>
        /// straight up blur (lose focus)
        /// </summary>
        /// <param name="ctrl">name of control to blur</param>
        public static void Blur(this WebControl ctrl)
        {
            var evt = new WebEvent(ctrl);
            evt.Blur();
        }

        /// <summary>
        /// set value of input(text) box then blur
        /// </summary>
        /// <param name="ctrl">control to blur</param>
        /// <param name="value">value to set to value of control</param>
        public static void Blur(this WebInput ctrl, string value)
        {
            ctrl.Value = value;
            var evt = new WebEvent(ctrl);
            evt.Blur();
        }

        /// <summary>
        /// set value of radio button then blur
        /// </summary>
        /// <param name="ctrl">control to blur</param>
        /// <param name="value">value to set to value of control</param>
        public static void Blur(this WebRadioGroup ctrl, bool value)
        {
            ctrl.Checked = value;
            var evt = new WebEvent(ctrl);
            evt.Blur();
        }

        /// <summary>
        /// set value of checkbox then blur
        /// </summary>
        /// <param name="ctrl">control to blur</param>
        /// <param name="value">value to set to value of control</param>
        public static void Blur(this WebCheckBox ctrl, bool value)
        {
            ctrl.Checked = value;
            var evt = new WebEvent(ctrl);
            evt.Blur();
        }

        /// <summary>
        /// set value of weblist then blur
        /// </summary>
        /// <param name="ctrl">control to blur</param>
        /// <param name="value">value to set to value of control</param>
        public static void Blur(this WebList ctrl, string value)
        {
            ctrl.Label = value;
            var evt = new WebEvent(ctrl);
            evt.Blur();
        }
    }

    /// <summary>
    /// A standard web html control
    /// </summary>
    public class WebControl : WebControlContainer
    {
        private WebEvent _event;
        private WebPage _page;

        // cached members
        private string _tag, _id, _name;

        /// <summary>
        /// The jQuery selector specific to this control (when coming from "GetControls" or similar nested control functions this contains this specific control's jQuery selector)
        /// </summary>
        private string _myjQuerySelector;

        /// <summary>
        /// Initializes a new instance of the WebControl class based on a jQuery selector
        /// </summary>
        /// <param name="container">
        /// The owning WebControlContainer
        /// </param>
        /// <param name="selector">
        /// The jQuery selector for this control
        /// </param>
        public WebControl(IQuailControl container, string selector)
            : base(container)
        {
            SetPage();
            MySelector = selector;
            Index = -1;
            OnParentControlChanged += OnParentControlContainerChanged;
        }

        /// <summary>
        /// Initializes a new instance of the WebControl class based on an existing WebControl
        /// </summary>
        /// <param name="control">
        /// The control to copy
        /// </param>
        public WebControl(WebControl control)
            : base(control.ParentControl)
        {
            SetPage();
            MySelector = control.MySelector;
            Index = control.Index;
            OnParentControlChanged += OnParentControlContainerChanged;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebControl"/> class.
        /// </summary>
        public WebControl()
        {
            OnParentControlChanged += OnParentControlContainerChanged;
        }

        /// <summary>
        /// Gets the Page that contains this control
        /// </summary>
        public override WebPage Page
        {
            get
            {
                return _page;
            }
        }

        /// <summary>
        /// Gets the current ActiveTask
        /// </summary>
        public override Task ActiveTask
        {
            get { return Page.ActiveTask; }
        }

        /// <summary>
        /// Gets the current Browser
        /// </summary>
        public override WebBrowser Browser
        {
            get { return Page.Browser; }
        }

        /// <summary>
        /// Gets this control's Html
        /// </summary>
        public string Html
        {
            get
            {
                return Browser.DoCommandNoLogging("getEval", "$(this.browserbot.findElement(\"" + Locator + "\")).html()");
            }
        }

        /// <summary>
        /// Gets this control's jQuery selector, including any parent selectors
        /// </summary>
        public override string Selector
        {
            get
            {
                var mysel = _myjQuerySelector;

                if (string.IsNullOrEmpty(mysel))
                    throw new NotSupportedException("Invalid Selector specified - my selector is blank!");

                var psel = ParentSelector;

                if (string.IsNullOrEmpty(psel))
                    return mysel;

                return psel + " " + mysel;
            }

            set
            {
                throw new NotSupportedException("Selector assignment is not allowed.  PLease use MySelector instead");
            }
        }

        /// <summary>
        /// Gets the control's parent selector
        /// </summary>
        public string ParentSelector
        {
            get
            {
                return ParentControl == null ? string.Empty : ParentControl.Selector;
            }
        }

        /// <summary>
        /// Gets or sets the selector specific to this control, not including any parent selectors
        /// </summary>
        public string MySelector
        {
            get
            {
                return _myjQuerySelector;
            }

            set
            {
                _myjQuerySelector = value;
            }
        }

        /// <summary>
        /// Gets the Selenium Locator for this control
        /// </summary>
        public string Locator
        {
            get
            {
                string sel = Selector;
                return !string.IsNullOrEmpty(sel) ? "jq=" + sel : string.Empty;
            }
        }

        /// <summary>
        /// Gets the HTML tag value for this control (INPUT, TEXTAREA, IMG, etc)
        /// </summary>
        public string Tag
        {
            get
            {
                if (_tag == null)
                    _tag = Attr(KnownAttribute.Tag);

                return _tag;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the control exists or not
        /// </summary>
        public override bool Exists
        {
            get
            {
                var task = BeginTask("Exists");
                try
                {
                    var result = Browser.DoQuick(() => Browser.SeleniumInterface.IsElementPresent(Locator));
                    if (task != null)
                        task.Finish(LogStatus.Information, result.ToString());
                    return result;
                }
                catch (SeleniumException seleniumException)
                {
                    // When the element is not present, I usually get an exception. I'm adding a catch specifically for this exception since it basically == false
                    var error = String.Format("*ERROR: Element jq={0} not found", this.Selector);
                    if (seleniumException.Message != error)
                        throw;
                    return false;
                }
                catch (Exception e)
                {
                    if (task != null)
                        task.Finish(e);
                    throw;
                }
                
            }
        }

        /// <summary>
        /// Gets an Event object for the control
        /// </summary>
        public WebEvent Event
        {
            get
            {
                if (_event == null)
                    _event = new WebEvent(this);

                return _event;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the control is visible.
        /// </summary>
        public override bool Visible
        {
            get
            {
                var task = BeginTask("Visible");
                var l = Locator;
                try
                {
                    // Browser.SeleniumInterface.IsVisible is a bit flaky - trying GetElementHeight > 0 instead
                    var result = Exists && Browser.SeleniumInterface.GetElementHeight(l) > 0;
                    task.Finish(result.ToString());

                    return result;
                }
                catch (Exception e)
                {
                    if (e.Message.Contains(l + " not found"))
                    {
                        // this exception can happen if the control is removed between "Exists" and "Browser.SeleniumInterface.GetElementHeight()"
                        // we can safely return false, since the control doesn't exist any more!
                        task.Finish(false);
                        return false;
                    }

                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether this <see cref="WebControl"/> is enabled.
        /// </summary>
        /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
        public override bool Enabled
        {
            get
            {
                return Exists && IsEditable;
            }
        }

        /// <summary>
        /// Gets or sets the Index of the control relative to other controls with the same selector
        /// </summary>
        public int Index { get; set; }

        /// <summary>
        /// Gets the ID attribute of the control
        /// </summary>
        public string ID
        {
            get
            {
                if (_id == null)
                    _id = Attr(KnownAttribute.ID);

                return _id;
            }
        }

        /// <summary>
        /// Gets the title attribute of the control
        /// </summary>
        public string Title
        {
            get { return Attr(KnownAttribute.Title); }
        }

        /// <summary>
        /// Gets the Name attribute of the control
        /// </summary>
        public string Name
        {
            get
            {
                if (_name == null)
                    _name = Attr(KnownAttribute.Name);

                return _name;
            }
        }

        /// <summary>
        /// Gets the InnerText of the control
        /// </summary>
        public string InnerText
        {
            get { return Attr(KnownAttribute.InnerText); }
        }

        /// <summary>
        /// Gets a value indicating whether the control is editable.
        /// </summary>
        public bool IsEditable
        {
            get
            {
                var task = BeginTask("Get IsEditable");
                try
                {
                    var result = Exists && Browser.SeleniumInterface.IsEditable(Locator);
                    task.Finish(result.ToString());
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets the Left coordinate of the control
        /// </summary>
        public decimal Left
        {
            get
            {
                var task = BeginTask("Get Left");
                try
                {
                    var result = Browser.SeleniumInterface.GetElementPositionLeft(Locator);
                    task.Finish(result.ToString());
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets the Top coordinate of the control
        /// </summary>
        public decimal Top
        {
            get
            {
                var task = BeginTask("Get Top");
                try
                {
                    var result = Browser.SeleniumInterface.GetElementPositionTop(Locator);
                    task.Finish(result.ToString());
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets the Height of the control
        /// </summary>
        public decimal Height
        {
            get
            {
                var task = BeginTask("Get Height");
                try
                {
                    var result = Browser.SeleniumInterface.GetElementHeight(Locator);
                    task.Finish(result.ToString());
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets Width.
        /// </summary>
        public decimal Width
        {
            get
            {
                var task = BeginTask("Get Width");
                try
                {
                    var result = Browser.SeleniumInterface.GetElementWidth(Locator);
                    task.Finish(result.ToString());
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
        }

        /// <summary>
        /// Gets the control's opacity
        /// </summary>
        public decimal Opacity
        {
            get
            {
                var task = BeginTask("Get Opacity");
                try
                {
                    var result = Browser.SeleniumTestSession.Processor.GetNumber("getOpacity", new[] { Locator });
                    task.Finish(result.ToString());
                    return result;
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }   
        }

        /// <summary>
        /// Returns a list of controls that are immediate children of the control
        /// </summary>
        /// <returns>A list of WebControl objects</returns>
        public List<WebControl> GetChildren()
        {
            return GetControls<WebControl>("> *");
        }

        /// <summary>
        /// Returns a list of controls that are visible (not fully implemented)
        /// </summary>
        /// <returns>
        /// the list of controls
        /// </returns>
        public List<WebControl> GetVisibleControls()
        {
            return GetControls<WebControl>(":visibleItems()");
        }

        /// <summary>
        /// Determines whether control has a specific class applied to it
        /// </summary>
        /// <param name="className">Name of class to look for</param>
        /// <returns>True if contains the passed class</returns>
        public bool HasClass(string className)
        {
            var classNames = Attr(KnownAttribute.ClassName).Split(' ');
            foreach (var c in classNames)
            {
                if (c.ToLower() == className.ToLower())
                    return true;
            }

            return false;
        }

        /// <summary>
        /// Highlights the control on the page (useful for debugging purposes)
        /// </summary>
        public void Highlight()
        {
            try
            {
                var task = BeginTask("Highlight");
                try
                {
                    Browser.SeleniumInterface.Highlight(Locator);
                    task.Finish();
                }
                catch (Exception e)
                {
                    task.Finish(e);
                    throw;
                }
            }
            catch (SeleniumException)
            {
                // ignore any SeleniumException
            }
        }

        /// <summary>
        /// Gets the Quail recommended selector for thil element
        /// </summary>
        /// <returns>A selector</returns>
        public string GetRecommendedSelector()
        {
            return new SelectorGeneratorFactory().GenerateSelector(this).Selector;
        }

        /// <summary>
        /// Method to exercise control functionality
        /// </summary>
        /// <param name="options">Exercise options</param>
        /// <param name="parms">Additional Parameters</param>
        public virtual void Exercise(ExerciseOptions options, params object[] parms)
        {
            throw new NotImplementedException("An Exercise function has not been implemented for this object.");
        }

        /// <summary>
        /// Gets the relative position of the element to the passed selector
        /// </summary>
        /// <param name="selector">Selector for control to find</param>
        /// <returns>Object with Left, Right, Top, and Bottom properties</returns>
        public WebControlPosition OffsetTo(string selector)
        {
            return OffsetTo(GetControl(selector));
        }

        /// <summary>
        /// Gets the relative position of the element to the passed element
        /// </summary>
        /// <param name="control">Control to compare to</param>
        /// <returns>Object with Left, Right, Top, and Bottom properties</returns>
        public WebControlPosition OffsetTo(WebControl control)
        {
            var diffLeft = Left - control.Left;
            var diffTop = Top - control.Top;
            var diffRight = Width - control.Width + diffLeft;
            var diffBottom = Height - control.Height + diffTop;

            return new WebControlPosition { Left = diffLeft, Top = diffTop, Bottom = diffBottom, Right = diffRight };
        }


        /// <summary>
        /// Converts a KnownAttribute to an html attribute string.
        /// </summary>
        /// <param name="attribute">The attribute.</param>
        /// <returns>The HTML attribute name</returns>
        /// <exception cref="SeleniumException"></exception>
        public static string KnownAttributeToString(KnownAttribute attribute)
        {
            switch (attribute)
            {
                case KnownAttribute.ID:
                    return "id";
                case KnownAttribute.Href:
                    return "href";
                case KnownAttribute.Target:
                    return "target";
                case KnownAttribute.Title:
                    return "title";
                case KnownAttribute.InputType:
                    return "type";
                case KnownAttribute.Src:
                    return "src";
                case KnownAttribute.Name:
                    return "name";
                case KnownAttribute.ClassName:
                    return "class";
                case KnownAttribute.OnClick:
                    return "onclick";
                case KnownAttribute.Tag:
                    return "tagName";
                case KnownAttribute.Alt:
                    return "alt";
                case KnownAttribute.Rel:
                    return "rel";
                case KnownAttribute.Content:
                    return "content";
                default:
                    throw new SeleniumException("Invalid KnownAttribute [" + attribute + "] passed to KnownAttributeToString.  Only certain KnownAttributes can be easily converted to Selenium string attributes.");
            }
        }

        /// <summary>
        /// Gets the value of a KnownAttribute
        /// </summary>
        /// <param name="attribute">The attribute to query</param>
        /// <returns>The attribute's value</returns>
        public string Attr(KnownAttribute attribute)
        {
            var l = Locator;
            if (string.IsNullOrEmpty(l))
                return string.Empty;

            switch (attribute)
            {
                case KnownAttribute.Tag:
                    return Page.Browser.DoCommand("getElementTag", l);

                case KnownAttribute.InnerText:
                    var task = BeginTask("Selenium.GetText");
                    try
                    {
                        var r = Browser.SeleniumInterface.GetText(l);
                        task.Rollback();
                        return r;
                    }
                    catch (Exception e)
                    {
                        task.Finish(e);
                        throw;
                    }

                case KnownAttribute.HrefAbsolute:
                    return UrlToAbsoluteUrl(Attr(l, KnownAttributeToString(KnownAttribute.Href)));
                case KnownAttribute.SrcAbsolute:
                    return UrlToAbsoluteUrl(Attr(l, KnownAttributeToString(KnownAttribute.Src)));
                default:
                    return Attr(l, KnownAttributeToString(attribute));
            }
        }

        /// <summary>
        /// Converts a relative or absolute Url to an absolute Url
        /// </summary>
        /// <param name="url">The url to convert</param>
        /// <returns>An absolute url</returns>
        public string UrlToAbsoluteUrl(string url)
        {
            if (string.IsNullOrEmpty(url))
                return url;

            var uri = new Uri(url, UriKind.RelativeOrAbsolute);

            if (uri.IsAbsoluteUri)
                return url;

            var pageuri = new Uri(Page.Location, UriKind.Absolute);
            uri = new Uri(pageuri, url);
            return uri.AbsoluteUri;            
        }

        /// <summary>
        /// Returns an array of attribute values for this control
        /// </summary>
        /// <param name="attributes">the attributes to find</param>
        /// <returns>an array of results</returns>
        public string[] Attrs(params KnownAttribute[] attributes)
        {
            return Attrs(Browser, Selector, attributes);
        }

        /// <summary>
        /// Returns an array of attribute values for the matching selector
        /// </summary>
        /// <param name="selector">the jQuery selector to use</param>
        /// <param name="attributes">the attributes to find</param>
        /// <returns>an array of results</returns>
        public string[] Attrs(string selector, params KnownAttribute[] attributes)
        {
            return Attrs(Browser, Selector + " " + selector, attributes);
        }


        /// <summary>
        /// Returns an array of attribute values for the matching selector
        /// </summary>
        /// <param name="browser">The browser</param>
        /// <param name="selector">the jQuery selector to use</param>
        /// <param name="attributes">the attribute to find</param>
        /// <returns>an array of results</returns>
        public static string[] Attrs(WebBrowser browser, string selector, params KnownAttribute[] attributes)
        {
            var task = browser.ActiveTask.BeginTask("Attrs");
            try
            {
                task.AddAttribute("Selector", selector);

                var js = "var x=new Array();$(this.browserbot.findElementOrNull(\"jq=" + selector +
                         "\")).each(function (i,e){";

                foreach (var attr in attributes)
                {
                    task.AddAttribute("Attribute", attr.ToString());

                    js += "y=";
                    switch (attr)
                    {
                        case KnownAttribute.InnerText:
                            js += "$(e).text()";
                            break;
                        case KnownAttribute.Href:
                            js += "$(e).attr('href')";
                            break;
                        case KnownAttribute.HrefAbsolute:
                            js += "e." + KnownAttributeToString(KnownAttribute.Href);
                            break;
                        case KnownAttribute.Src:
                            js += "$(e).attr('src')";
                            break;
                        case KnownAttribute.SrcAbsolute:
                            js += "e." + KnownAttributeToString(KnownAttribute.Src);
                            break;
                        default:
                            js += "e." + KnownAttributeToString(attr);
                            break;
                    }
                    js += ";x.push(y?y:'');";
                }

                js += "});x";

                var result = browser.DoCommandToStringArray("getEval", js);

                task.Finish("Results: " + result.Length);

                return result;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Gets a custom attribute from the control
        /// </summary>
        /// <param name="attr">the custom attribute name</param>
        /// <returns>the value of the attribute</returns>
        public string CustomAttr(string attr)
        {
            return Attr(Locator, attr);
        }

        /// <summary>
        /// Performs the click event on the control
        /// <param name="pageToSync">Specify the page you want to sync to</param>
        /// </summary>
        public void Click(WebPage pageToSync)
        {
            Click(pageToSync != null, pageToSync);
        }

        /// <summary>
        /// Performs the click event on the control
        /// <param name="pagesToSync">Specify the pages you want to sync to</param>
        /// <returns>Returns the first page in the enumeration that matches the page</returns>
        /// </summary>
        public WebPage Click(IEnumerable<WebPage> pagesToSync)
        {
            Click();

            var thepage = pagesToSync.Where(p => p.Exists).FirstOrDefault();

            if (thepage == null)
                throw new WebPageNotFoundException(null, "No matching page found");

            return thepage;
        }

        /// <summary>
        /// Performs the click event on the control and calls SyncAnyPage()
        /// </summary>
        public void Click()
        {
            Click(true, null);
        }

        /// <summary>
        /// Performs the click event on the control and calls SyncAnyPage()
        /// <param name="sync">Set to False to avoid calling SyncAnyPage()</param>
        /// </summary>
        public void Click(bool sync)
        {
            Click(sync, null);
        }

        /// <summary>
        /// Performs the click event on the control
        /// <param name="sync">Cause a page sync.  Defaults to TRUE</param>
        /// <param name="pageToSync">Page you want to sync to.  Defaults to NULL, which will SyncAnyPage()</param>
        /// </summary>
        public virtual void Click(bool sync, WebPage pageToSync)
        {
            var task = sync
                           ? BeginTask("ClickAndSync")
                           : null;
            try
            {
                Event.Click();
                if (sync)
                {
                    if (pageToSync == null)
                    {
                        Page.SyncAnyPage();
                    }
                    else
                    {
                        pageToSync.Sync();
                    }
                }

                if (task != null)
                    task.Finish();
            }
            catch (Exception e)
            {
                if (task != null)
                    task.Finish(e);
                throw;
            }
        }

        /// <summary>
        /// Performs the click event on the control at a specific coordinate relative to the control
        /// </summary>
        /// <param name="x">The left coordinate</param>
        /// <param name="y">The top coordinate</param>
        public void ClickAt(int x, int y)
        {
            Event.ClickAt(x, y);
        }

        /// <summary>
        /// Begins a task for logging
        /// </summary>
        /// <param name="taskname">The name of the task</param>
        /// <returns>A Task object</returns>
        public Task BeginTask(string taskname)
        {
            return BeginTask(taskname, null);
        }

        /// <summary>
        /// The begin task.
        /// </summary>
        /// <param name="taskname">The taskname.</param>
        /// <param name="message">The message.</param>
        /// <returns>A valid Task object</returns>
        public Task BeginTask(string taskname, string message)
        {
            var task = ActiveTask.BeginTask(taskname);
            task.AddAttribute("Type", GetType().ToString());
            task.AddAttribute("Selector", Selector);
            if (!string.IsNullOrEmpty(message))
                task.AddAttribute("Details", message);

            return task;
        }

        /// <summary>
        /// Blanks out the value of the WebControl
        /// </summary>
		public virtual void Clear()
		{
			throw new NotImplementedException("Clear is not available for this WebControl Type.");
		}

        /// <summary>
        /// The set page.
        /// </summary>
        private void SetPage()
        {
            if (_page != null)
                _page.OnSyncIsCompleteEvent -= OnPageSync;
            if (ParentControl is WebPage)
                _page = ParentControl as WebPage;
            else if (ParentControl is WebControl)
                _page = (ParentControl as WebControl).Page;
            else
                _page = null;

            if (_page != null)
                _page.OnSyncIsCompleteEvent += OnPageSync;
        }

        protected virtual void OnPageSync(IWebPage page)
        {
            // clear cached stuff when the owning page syncs
            _id = null;
            _name = null;
            _tag = null;
        }

        /// <summary>
        /// implementation of the Attr function for both public versions
        /// </summary>
        /// <param name="loc">the selenium locator</param>
        /// <param name="attribute">the attribute name</param>
        /// <returns>the attribute value</returns>
        private string Attr(string loc, string attribute)
        {
            var task = BeginTask("WebControl.Attr");
            task.AddAttribute("Attr", attribute);
            try
            {
                var r = Browser.SeleniumInterface.GetAttribute(string.Format("{0}@{1}", loc, attribute));
                task.Finish(r);
                return r;
            }
            catch (SeleniumException se)
            {
                // absorb pointless attribute error message
                if (se.Message.Contains("Could not find element attribute"))
                {
                    task.Finish();
                    return string.Empty;
                }

                task.Finish(se);
                throw;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }

        private void OnParentControlContainerChanged(IQuailControl oldParent, IQuailControl newParent)
        {
            SetPage();
        }

        /// <summary>
        /// Contains the web control positional coordinates
        /// </summary>
        public class WebControlPosition
        {
            /// <summary>
            /// Gets or sets the left coordinate
            /// </summary>
            public decimal Left { get; set; }

            /// <summary>
            /// Gets or sets the top coordinate
            /// </summary>
            public decimal Top { get; set; }

            /// <summary>
            /// Gets or sets the right coordinate
            /// </summary>
            public decimal Right { get; set; }

            /// <summary>
            /// Gets or sets the bottom coordinate
            /// </summary>
            public decimal Bottom { get; set; }
        }

        /// <summary>
        /// Exception for the WebControl object
        /// </summary>
        public class WebControlException : WebPageException
        {
            /// <summary>
            /// Initializes a new instance of the WebControlException class
            /// </summary>
            /// <param name="control">
            /// The control with the exception
            /// </param>
            /// <param name="message">
            /// The message for the exception
            /// </param>
            public WebControlException(WebControl control, string message)
                : base(control.Page, message)
            {
                Control = control;
            }

            /// <summary>
            /// Gets the Control for this exception
            /// </summary>
            public WebControl Control { get; private set; }
        }
    }
}